AWSCLI、Python(boto3)などからS3フォルダを削除してみる(第2版)

AWSCLI、Python(boto3)などからS3フォルダを削除してみる(第2版)

Clock Icon2024.10.14

AWS事業本部コンサルティング部の石川です。今でもよくご覧いただいているブログ AWSCLI、Python(boto3)などからS3フォルダを削除してみるを6年ぶりに改定しました。以前のPython(boto3)の実装よりもスッキリわかり易く再実装しました。また、並列処理版もサンプルを追加しています。

https://dev.classmethod.jp/articles/20180625-how-to-delete-s3folder/

検証用データ

検証用データの作成

検証用フォルダ・ファイルをのポチポチ作成するのは無理なので以下のスクリプトで、2999個のファイルを一括作成します。S3バケットの下のdelete-folderというフォルダ階層の下に、file_0000.txt 〜 file_2998.txt のファイルを作成します。

import boto3
import os

# S3クライアントの作成
PROFILE_NAME = 'ishikawa' # Optional
session = boto3.Session()
if PROFILE_NAME in boto3.Session().available_profiles:
    session = boto3.Session(profile_name=PROFILE_NAME)
s3 = session.client('s3')

# バケット名の指定
bucket_name = 'cm-test-20241013'
folder_name = "delete-folder"

# ファイル数
file_num=2999

# ファイルを生成してアップロード
for i in range(file_num):
    file_name = f'file_{i:04d}.txt'
    content = f'This is file number {i}'

    with open(file_name, 'w') as f:
        f.write(content)
    s3.upload_file(file_name, bucket_name, f'{folder_name}/{file_name}')
    os.remove(file_name)

    print(f'Uploaded {folder_name}/{file_name} to S3')
print('Finished uploading {num} files to S3')

検証データの例

以下のようなデータを事前に作成しました。

% aws s3 ls s3://cm-test-20241013/delete-folder/
2024-10-14 19:34:54         21 delete-folder/file_0000.txt
2024-10-14 19:34:54         21 delete-folder/file_0001.txt
2024-10-14 19:34:54         21 delete-folder/file_0002.txt
:
:

補足: boto3.resourceからboto3.clientへ

6年前はboto3.resourceを使って検証ファイルを作っていましたが、現在はboto3.clientを使っています。その理由は、boto3.resourceは、新機能の追加は行われず、将来的には別のSDKに置き換わる可能性があります。新規開発ではclientインターフェースの使用を検討するのが良いでしょう。

https://dev.classmethod.jp/articles/aws-python-sdk-resource-interface-deperecation-warning/

方法1: マネジメントコンソールからS3フォルダの削除

マネジメントコンソールからS3フォルダの削除する方法です。削除したいフォルダを選択して、[Delete]ボタンを押します。

20241014-s3-folder-delete-1

permanently delete」(日本語の場合は、「完全に削除」)を入力して、[Delete Objects]ボタンを押します。

20241014-s3-folder-delete-2

削除進行中は、削除の状況が表示されます。右上の[cancel]ボタンで中断も可能です。

20241014-s3-folder-delete-3

正常に終わると、以下の画面が表示されます。2999ファイルの削除時間を計測すると、約5秒程度でした。

20241014-s3-folder-delete-4

方法2: AWSCLIでS3フォルダの削除する

S3フォルダ名を指定して削除するには、--recursiveを指定します。S3フォルダごと削除されたことが確認できます。

$ time aws s3 rm s3://cm-test-20241013/delete-folder --recursive
delete: s3://cm-test-20241013/delete-folder/file_0000.txt
delete: s3://cm-test-20241013/delete-folder/file_0001.txt
delete: s3://cm-test-20241013/delete-folder/file_0005.txt
:
:
delete: s3://cm-test-20241013/delete-folder/file_2998.txt

$ aws s3 ls s3://cm-test-20241013/delete-folder
$

2999ファイルの削除時間を計測すると、約15秒程度でした。

4.55s user 0.58s system 38% cpu 13.439 total

なお、、--recursive指定を忘れると、削除されませんのでご注意ください。

$ aws s3 rm s3://cm-test-20241013/delete-folder
delete: s3://cm-test-20241013/delete-folder

$ aws s3 ls s3://cm-test-20241013/delete-folder/
2024-10-14 19:34:54         21 delete-folder/file_0000.txt
2024-10-14 19:34:54         21 delete-folder/file_0001.txt
2024-10-14 19:34:54         21 delete-folder/file_0002.txt
:
:

補足:オブジェクト数のカウント

なお、オブジェクト数は以下のコマンドで高速に取得できます。下記の方法では、S3基盤でオブジェクト数をカウントし、レスポンスにはオブジェクト数のみが含まれるため、オブジェクトリスト全体を取得するよりは高速に動作します。

$ time aws s3api list-objects-v2 --bucket cm-test-20241013 --prefix delete-folder/ --query 'length(Contents)'
2999

0.42s user 0.12s system 54% cpu 0.996 total

方法3: Python(boto3)でS3フォルダを削除する方法

paginatorによるページング処理することで、1000以上オブジェクトをコピーできるように実装しました。最も一般的な方法です。

簡単にコードの解説をすると、ページ毎の削除対象のキーのリストを作り、s3.delete_objects関数に渡して削除します。ページは1000オブジェクト毎であり、今回は2999オブジェクトなので、s3.delete_objects関数を3回呼び出しています。

import boto3

def delete_s3_folder(s3, bucket_name, folder_name, dryrun=False):
    paginator = s3.get_paginator('list_objects_v2')

    # フォルダ内のオブジェクトを1000件ずつ取得
    for page in paginator.paginate(Bucket=bucket_name, Prefix=folder_name):
        if 'Contents' in page:
            objects_to_delete = [{'Key': obj['Key']} for obj in page['Contents']]
            if dryrun:
                print(f"Would Delete between {page['Contents'][0]['Key']} and {page['Contents'][-1]['Key']}")
            else:
                # 削除リクエストを送信
                s3.delete_objects(
                    Bucket=bucket_name,
                    Delete={'Objects': objects_to_delete}
                )
                print(f"Deleted between {page['Contents'][0]['Key']} and {page['Contents'][-1]['Key']}")
    if not dryrun:
        print(f"Deleted Objects in folder '{folder_name}'")

# S3クライアントの作成
PROFILE_NAME = 'ishikawa' # Optional
session = boto3.Session()
if PROFILE_NAME in boto3.Session().available_profiles:
    session = boto3.Session(profile_name=PROFILE_NAME)
s3 = session.client('s3')

# 削除対象
bucket_name = 'cm-test-20241013'
folder_name = 'delete-folder/'

delete_s3_folder(s3, bucket_name, folder_name)

実際に実行すると、2999ファイルの削除時間を計測すると、約6秒程度でした。

% time python delete_s3_folder.py
Deleted between delete-folder/file_0000.txt and delete-folder/file_0999.txt
Deleted between delete-folder/file_1000.txt and delete-folder/file_1999.txt
Deleted between delete-folder/file_2000.txt and delete-folder/file_2998.txt
Deleted Objects in folder 'delete-folder/'

0.54s user 0.09s system 3% cpu 18.728 total

十分高速なのですが、マネジメントコンソールの5秒に比べるとちょっと遅いです。

恐らくページ毎に2秒かかるという仮説が立てられます。では、試してみましょう。

方法4: Python(boto3)でS3フォルダを削除する方法(マルチスレッド版)

上記のPythonプログラムをThreadPoolExecutorを用いて、マルチスレッドに書き換えました。

import boto3
from concurrent.futures import ThreadPoolExecutor

# 並列数
MAX_WORKERS = 10

def delete_objects(s3, page, dryrun):
    if 'Contents' in page:
        objects_to_delete = [{'Key': obj['Key']} for obj in page['Contents']]
        if dryrun:
            print(f"Would Delete between {page['Contents'][0]['Key']} and {page['Contents'][-1]['Key']}")
        else:
            # 削除リクエストを送信
            s3.delete_objects(
                Bucket=bucket_name,
                Delete={'Objects': objects_to_delete}
            )
            print(f"Deleted between {page['Contents'][0]['Key']} and {page['Contents'][-1]['Key']}")

def delete_s3_folder_multithreads(s3, bucket_name, folder_name, max_workers=MAX_WORKERS, dryrun=False):
    paginator = s3.get_paginator('list_objects_v2')
    pages = paginator.paginate(Bucket=bucket_name, Prefix=folder_name)

    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        for page in pages:
            executor.submit(delete_objects, s3, page, dryrun)

# S3クライアントの作成
PROFILE_NAME = 'ishikawa' # Optional
session = boto3.Session()
if PROFILE_NAME in boto3.Session().available_profiles:
    session = boto3.Session(profile_name=PROFILE_NAME)
s3 = session.client('s3')

# 削除対象
bucket_name = 'cm-test-20241013'
folder_name = 'delete-folder/'

# 実行
delete_s3_folder_multithreads(s3, bucket_name, folder_name)

実際に実行すると、2999ファイルの削除時間を計測すると、2秒未満でした。仮説は証明できました。圧倒的にプログラムの方が速くなりました。

並列数が「10」なので1万ファイルまでの削除は、単位時間の2秒で削除できると考えられます。なお、並列数はプログラムなのでいくらでも増やせますが、APIのスロットリングなどを考慮すると無制限に並列実行できるとは限りません。

検証結果

前回のブログと同じく、フォルダの2999のS3オブジェクトを削除する方式と処理時間を計測しました。方法1と方法4は、最速でした。

  • 簡単にフォルダを削除するのなら方法1
    • マネジメントコンソールで「permanently delete」を入力する必要あり(ちょっと面倒?)
  • プログラム(Python)から速くフォルダを削除するのなら方法4
  • コマンドでフォルダを削除するのなら方法2
方式 処理時間(秒)
方法1: マネジメントコンソールからS3フォルダの削除 5
方法2: AWSCLIでS3フォルダの削除する 32
方法3: Python(boto3)でS3フォルダを削除する方法 6
方法4: Python(boto3)でS3フォルダを削除する方法(マルチスレッド版) 2

最後に

比較的よくご覧いただいてる AWSCLI、Python(boto3)などからS3フォルダを削除してみるを6年ぶりに改定しました。2018年から、S3のベースラインパフォーマンスの改善やレートリミット上限が緩和され、最新の状況を測定したいと考えていたところでした。当時は、「list_objects」から「list_objects_v2」の移行期でしたので、手厚く解説していましたが、今回は省略しました。しかし、paginatorオブジェクトのインスタンスを作る際には、「list_objects_v2」を指定しています。

今回の第2版では、ページ単位でオブジェクトを削除する方式を採用し、かつマルチスレッドでフォルダを削除する方法についても加筆しています。AWSの各種ログファイルは、小さく大量のファイルが多く存在するため、パフォーマンスを改善したいというユースケースは多いのではないでしょうか。今後はこちらのブログを参照していただけたら幸いです。

合わせて読みたい

https://dev.classmethod.jp/articles/20241013-awscli-python-boto3-s3/

この記事をシェアする

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.